# -*- coding: ISO-8859-1 -*-

from thread import start_new_thread as nthread
from math import *
import math # It is needed for the Bot.isMathExpression method.
import traceback
import re

import meteo
import insults
import magazine
import irc
import google

# IRCBot by Tharis
# perms.sec update
# ^ self.members update
# $calc time limit
# isMathExpression workin' better
# flood limit
# $help cmd
# Output to console should be controlled by the debug flag, or verbose flag


class Bot():
    """Connects to an irc server, logins into a channel and calls the
    stayConnected method.

    Makes the calls to irc.IRC_Class, meteo.Client and insults.Insult_Manager
    internally.
    """

    def __init__(self, nickname, username, password, server, channel,
                port=6667):
        """Loads the needed modules,sets up a few global constants, makes the
        login and calls the infinite loop.
        """
        self.irc_client = irc.IRC_Class()
        self.temp = meteo.Client()
        self.insult_manager = insults.Insult_Manager()
        self.google_client = google.Google()

        self._reloadPerms()

        self.server = server
        self.port = port
        self.nickname = nickname
        self.password = password
        self.username = username
        self.channel = channel

        self.irc_client.login(self.server, self.port, self.nickname,
            self.password, self.username, self.channel)
        self.stayConnected(self.channel)

    def isCmd(self, msg):
        """Checks if the message is a bot command.

        Returns True if it is, otherwise returns False.
        """
        try:
            if msg[0] == '$':
                return True
            else:
                return False
        except: # TODO: Find out the specific exception to catch
                return False

    def isMathExpression(self):
        """Checks if the message is a math expression.

        Returns True if it is, otherwise returns False.
        """
        pattern = re.compile("[\d+*-/() ]")

        parsed_msg = self.info["target"]

        # A "x^y" should be a "pow(x, y)" or "x**y"
        parsed_msg = parsed_msg.replace("^", "**")

        result = re.findall(pattern, parsed_msg)

        if not result and len(parsed_msg) < 100:
            # Didn't find any letters, basic math equation
            return True

        ##### Begin to get the words from the parsed_msg

        # Remove symbols
        tmp_msg = parsed_msg
        tmp_msg = tmp_msg.replace("+", " ")
        tmp_msg = tmp_msg.replace("-", " ")
        tmp_msg = tmp_msg.replace("/", " ")
        tmp_msg = tmp_msg.replace("*", " ")
        tmp_msg = tmp_msg.replace("(", " ")
        tmp_msg = tmp_msg.replace(")", " ")
        tmp_msg = tmp_msg.split(" ")

        # There can be several white spaces in the splited list
        # so we need to remove them
        words_msg = []

        for value in tmp_msg:
            # We take the opportunity and use the isdigit() to also remove
            # the numbers
            if value == "" or value.isdigit():
                pass
            else:
                words_msg.append(value)
        #####

        # Create a list that will hold all the methods from the math module
        # except it wont include the stuff like __add__
        math_methods = []
        for method in dir(math):
            if method.startswith("__"):
                pass
            else:
                math_methods.append(method)

        for word in words_msg:
            if word not in math_methods:
                return False
            else:
                pass

        # Passed all tests, good to go
        return True

    def stayConnected(self, channel):
        """Main loop.

        Deals with the internal calls to the various methods defined in this
        class. Also deals with the ping command sent by the server.

        Doesn't return anything, because it runs indefinitely.
        """
        ## TODO: Don't forget to add the meteo thing.
        ## TODO: Remember what I meant to say by using that
        self.cmds = {"imitar": self.imitar,
            "insult": self.insult,
            "perms": self.perms,
            "eval": self.calc,
            "calc": self.calc,
            "temp": self.meteo,
            "meteo": self.meteo,
            "list": self.list,
            "artigo": self.article,
            "article": self.article,
            "mag_update": self.mag_update,
            "help": self.help,
            "link": self.link,
            "google": self.google,
            "addperm": self.addPerms,
            "rmperm": self.rmPerms}

        self.permissions = {"imitar": True,
            "insult": False, "perms": False,
            "eval": False, "calc": False,
            "temp": False, "meteo": False,
            "list": False, "artigo": False,
            "article": False, "mag_update": True,
            "help": False, "link": False,
            "google": False, "addperm": True,
            "rmperm": True}

        self.help_list=["\t$help [command]",
            "\t$google [pesquisa]",
            "\t$calc [math_expression]" \
                "(includes all python's math module functions)",
            "\t$eval [math_expression]" \
                "(< 100 characters) ^ the same", # FIXME: Redundant? Same as ^
            "\t$perms", "\t$imitar [nickname] | all | stop",
            "\t$insult [nickname]", "\t$temp [cidade]", "\t$meteo [cidade]",
            "\t$artigo [palavra_do_titulo]",
            "\t$article [palavra_do_titulo]", # FIXME: Redundant? Same as ^
            "\t$mag_update",
            "\t$link",
            "\t$addperm [nick]",
            "\t$rmperm [nick]",
            "\t\t Bot Written by Tharis aka tharis20 and rolando2424"]

        self.info = {"copyCat1": False,
            "copyCatAll": False}

        while True:
            msg = self.irc_client.IRC.recv(512)
            print msg

            self.msg_1=msg

            self.info["user"] = self.irc_client.parseUsr(msg)
            self.info["msg"] = self.irc_client.parseMsg(msg)
            self.info["target"] = self.irc_client.parseTarget(msg)

            if "PING :" in msg:
                self.irc_client.pong(msg)

            if self.isCmd(self.info["msg"]):
                try:
                    command = self.irc_client.parseCmd(self.info["msg"])
                    if not self.permissions[command]:
                        nthread(self.cmds[command], ()) # Start new thread
                    elif self.permissions[command] and \
                            self.info["user"] in self.members:
                        print command
                        nthread(self.cmds[command], ())
                    elif self.permissions[command] and \
                            self.info["user"] not in self.members:
                        nthread(self.irc_client.sendChannel, (self.channel,
                            "Command execution denied!"))

                except: # TODO: Find out the specific exception to catch
                    self.irc_client.sendChannel(self.channel,
                        "Unknown command")
                    print "-" * 30
                    traceback.print_exc()
                    print "-" * 30

            elif self.info["copyCatAll"] and "PRIVMSG" in msg:
                self.copyCatAll()
            elif self.info["copyCat1"] and "PART" not in msg and \
                    "QUIT" not in msg:
                self.copyCat()

            elif "this is" in self.info["msg"].lower():
                self.easter_egg1()
            elif "i said what what" in self.info["msg"].lower():
                self.easter_egg2()

    def calc(self):
        """Checks if the message is a Math expression and passes it to the
        python interpreter.

        Doesn't return anything.
        """
        if self.isMathExpression():
            result = str(eval(self.info["target"].replace('^', '**')))
            self.irc_client.sendChannel(self.channel, result)
        else:
            self.irc_client.sendChannel(self.channel, "Invalid Expression")

    def perms(self):
        """Sends to the channel the nicknames of the people who have full
        permissions to use the bot.

        Doesn't return anything.
        """
        to_send = ""
        for i in self.members:
            to_send += i+" "
        self.irc_client.sendChannel(self.channel, to_send)

    def imitar(self):
        """Sets the nickname of the user it should imitate.

        It is also possible to imitate all users with the command $imitar all.
        THINK TWICE BEFORE YOU USE THIS.

        To stop the imitation, type the $imitar stop.

        Doesn't return anything.
        """
        if self.info["target"] == "all" and self.info["user"] in self.members:
            self.info["copyCatAll"] = True
        elif self.info["target"] == "stop" and \
                self.info["user"] in self.members:
            self.info["copyCat1"] = self.info["copyCatAll"] = False
        elif self.info["target"] == self.nickname or \
                self.info["target"] == "tharis20":
            self.info["cCtarget"] = self.info["user"]
            self.info["copyCat1"] = True
        else:
            self.info["cCtarget"] = self.info["target"]
            self.info["copyCat1"] = True

    def help(self):
        """Sends the bot's help to the channel.

        Doesn't return anything.
        """
        for i in self.help_list:
            if self.info["target"] in i:
                self.irc_client.sendChannel(self.channel, i)

    def list(self):
        """Sends the bot's help as a privmsg to the user.

        Doesn't return anything.
        """
        ## FIXME: Isn't this pretty much the same this as the help method
        ## above?
        ## EDIT: It seems that the $help prints to the channel and the list
        ## sends a private message to the user
        self.irc_client.sendChannel(self.channel, "Big List, pvt instead")
        for i in self.help_list:
            self.irc_client.sendPrivmsg(self.info["user"], i)

    def easter_egg1(self):
        """Returns madness.
        """
        if self.nickname in self.msg_1:
            self.irc_client.sendPrivmsg(self.info["user"],
                "SPARTAAAAAAAAAAAAAAAAAAAA!")
        else:
            self.irc_client.sendChannel(self.channel,
                "SPARTAAAAAAAAAAAAAAAAAAAA!")

    def easter_egg2(self):
        """Another easter egg created because of triton.
        """
        if self.nickname in self.msg_1:
            self.irc_client.sendPrivmsg(self.info["user"], "IN THE BUTT")
        else:
            self.irc_client.sendChannel(self.channel, "IN THE BUTT")

    def copyCat(self):
        """Sends to the channel the same message typed by the nickname the the
        bot is currently imitating.

        Doesn't return anything.
        """
        if self.info["user"] == self.info["cCtarget"]:
            msg = self.info["msg"]
            self.irc_client.sendChannel(self.channel, msg)

    def copyCatAll(self):
        """Sends to the channel the messages typed by all users in that
        channel.

        USE WISELY!

        Doesn't return anything.
        """
        try:
            msg = self.info["msg"]
            self.irc_client.sendChannel(self.channel, msg)
        except: # TODO: Find out the specific exception to catch
            pass

    def insult(self):
        """Sends to the channel a random insult from the insults module.

        Doesn't return anything.
        """

        print self.info["target"]

        if not self.info["target"]:
            self.irc_client.sendChannel(self.channel,
                "You need to type a name")

        elif self.info["target"] == "tharis20" or \
                self.info["target"] == self.nickname:
            print "Attempt to insult myself or master"
            self.irc_client.sendChannel(self.channel,
                "Sorry, I do not insult my master nor me")
            self.irc_client.sendChannel(self.channel,
                "But I can insult you :p")

            insult = self.insult_manager.get_insult()
            # The $ indicates the place of the nickname to insult
            insult = insult.replace("$", self.info["target"])
            self.irc_client.sendChannel(self.channel, insult)
        else:
            print "Insult Normal Target"
            insult = self.insult_manager.get_insult()
            # The $ indicates the place of the nickname to insult
            insult = insult.replace("$", self.info["target"])
            self.irc_client.sendChannel(self.channel, insult)

    def meteo(self):
        """Sends to the channel the temperature and weather conditions of a
        city if it exists in the meteo module database.

        Doesn't return anything.
        """

        ## FIXME: city's names come with no spaces on it ("vianadocastelo",
        ## instead of "viana do castelo")
        # self.info["target"] uses a replace(" ", ""), so we use a work around
        #city = self.info["target"]

        city = self.info["msg"].replace("\r\n", "")
        city = city.split(" ", 1)[1] # get the name of the city

        if city.lower() in self.temp.known_cities:
            ## TODO: Maybe we should be able to specify what to return from
            ##       the meteo module
            #pdb.set_trace()
            (place, weather, temperature, vel_wind, dir_wind, humidity, rain,
                    pressure, mist, snow) = self.temp.get_information(city)

            self.irc_client.sendChannel(self.channel,
                "Condicao atmosferica: " + weather)
            self.irc_client.sendChannel(self.channel,
                "Temperatura: %sºC" % temperature)

        else:
            self.irc_client.sendChannel(self.channel,
                "Não encontro isso na minha base de dados")
            self.irc_client.sendPrivmsg(self.info["user"],
                "Aqui estão as cidades disponíveis")

            for cities in self.temp.known_cities:
                self.irc_client.sendPrivmsg(self.info["user"],
                    cities.capitalize())

    def link(self):
        """Sends to the channel a url with the link to the website of this
        project.

        Doesn't return anything.
        """
        self.irc_client.sendChannel(self.channel,
            "\thttp://code.google.com/p/ircbot-in-python/")

    def article(self):
        """Searches the Portugal-a-Programar magazine database for a word and
        sends to the channel the articles that contain that word.

        Doesn't return anything.
        """
        articles = open("cache.mag", 'r').readlines()
        done = False
        to_send = []
        for i in articles:
            num = articles.index(i)
            if len(self.info["target"]) < 3:
                self.irc_client.sendChannel(self.channel,
                    "Length of word must me equal or greater than 3")
                break
            elif self.info["target"].lower() in i.lower() and num % 2 == 0:
                ## FIXME: Ask tharis to comment this section and also the
                ##        magazine module
                i = ("Artigo: " + i + " | " + articles[num+1])
                i = i.replace('\r\n', '').replace('\n', '')
                to_send.append(i)
                done = True
            elif num == len(articles)-1 and not done:
                to_send.append("No article for that word")

        print to_send

        if len(to_send) > 3:
            self.irc_client.sendChannel(self.channel,
                "Found many articles, pvt instead")
            for i in to_send:
                self.irc_client.sendPrivmsg(self.info["user"], i)
        else:
            for i in to_send:
                self.irc_client.sendChannel(self.channel, i)

    def mag_update(self):
        """Updates the magazine local cache.

        Doesn't return anything.
        """
        try:
            magazine.Magazine(" ")
            self.irc_client.sendChannel(self.channel, "Magazine Cache Updated")
        except: # TODO: Find out the specific exception to catch
            self.irc_client.sendChannel(self.channel,
                "Unable to Update Magazine Cache")

    def google(self):
        """Makes a search in google and prints it to the channel.

        Doesn't return anything.
        """
        google_client = google.Google()

        ## FIXME: Just like the self.meteo, if we join the stuff, it doesn't
        ## work. This should probably be fixed, like, yesterday.
        ## Seriously, I spent a day and a half wondering why the search for
        ## several words worked in the testcase but not in the irc channel
        ## when it hit me like a truck wheel.
        string = self.info["msg"].replace("\r\n", "")
        string = string.split(" ", 1)[1] # get the name of the city
        print string
#        site = google_client.search(self.info["target"])
        site = google_client.search(string)
        if site != 1:
            self.irc_client.sendChannel(self.channel, site)
        else:
            self.irc_client.sendChannel(self.channel, "Site not found")

    def _reloadPerms(self):
        """Reads the permissions file and creates a list of nicks that
        can fully use the bot.

        Doesn't return anything.
        """
        perms = open("perms.sec", "r")
        members = perms.readlines()
        self.members = []
        for nickname in members:
            nickname = nickname.replace("\r\n", "").replace("\n", "")
            self.members.append(nickname)
        perms.close()

    def addPerms(self):
        """Adds a nick to the permit ions file.

        Doesn't return anything.
        """
        if self.info["user"] in self.members:
            nickname = self.info["target"]
            if nickname in self.members: # No need to add a person that already
                                         # has the permissions in the bot
                if self.nickname in self.msg_1:
                    self.irc_client.sendPrivmsg(self.info["user"],
                            "That name is already in the database.")
                else:
                    self.irc_client.sendChannel(self.channel,
                            "That name is already in the database.")
            else:
                perms = open("perms.sec", "a")
                perms.write(nickname + "\n")
                perms.close()
                self._reloadPerms()
        else:
            if self.nickname in self.msg_1:
                self.irc_client.sendPrivmsg(self.info["user"],
                        "Command execution denied!")
            else:
                self.irc_client.sendChannel(self.channel,
                        "Command execution denied!")

    def rmPerms(self):
        """Removes a nick from the permissions file.

        Doesn't return anything.
        """
        if self.info["user"] in self.members:
            nickname = self.info["target"]
            if nickname not in self.members: # It already doesn't have the
                                             # permission in the bot
                if self.nickname in self.msg_1:
                    self.irc_client.sendPrivmsg(self.info["user"],
                            "That name is not in the database.")
                else:
                    self.irc_client.sendChannel(self.channel,
                            "That name is not in the database.")
            else:
                self.members.remove(nickname)

                perms = open("perms.sec", "w")

                for member in self.members:
                    perms.write(member + "\n")
                perms.close()

                self._reloadPerms()
        else:
            if self.nickname in self.msg_1:
                self.irc_client.sendPrivmsg(self.info["user"],
                        "Command execution denied!")
            else:
                self.irc_client.sendChannel(self.channel,
                        "Command execution denied!")


def get_arguments():
    """Parses the arguments passed during runtime.

    Returns a object containing the values for the options.
    """

    parser.add_option("-n", "--nickname", dest="nickname",
        help="Nickname for the bot")
    parser.add_option("-u", "--username", dest="username",
        help="Username for the bot")
    parser.add_option("-p", "--password", dest="password",
        help="Password for the bot")
    parser.add_option("-s", "--server", dest="server",
        help="The server the bot should join")
    parser.add_option("-c", "--channel", dest="channel",
        help="The channel the bot should join (Can only join one channel)")
    parser.add_option("--port", dest="port", type="int", default=6667,
        help="Choose the port to connect to. Default: 6667")
    parser.add_option("-d", "--debug", dest="debug", action="store_true",
        help="Shortcut to place the above variables in the way that the " \
            "original developers can make the tests. Should not be used by " \
            "anyone else.")

    (options, argv) = parser.parse_args()

    return options


if __name__ == "__main__":
    import optparse
    parser = optparse.OptionParser()

    opt = get_arguments()

    if opt.debug: # Set up the way the original coders like it
        bot = Bot("imitationMonkey", "macaco", "computador", "irc.ptnet.org",
            "#fuckie")
    # Only create the bot if we have all the information
    elif opt.nickname and opt.username and opt.password and opt.server and \
            opt.channel:
        bot = Bot(opt.nickname, opt.username, opt.password, opt.server,
            opt.channel, opt.port)
    else:
        print "Error: Not enough information passed."
        parser.print_help()
